/*
MySQL协议简短说明
大致上采用一问多答的方式，由client发问。
所有包头前3个字节为整个包的大小，第3个字节为此次应答中包的序号。
故单包大小上限(2^24)-1=(16M-1)字节，序号0~255循环使用。
client端第一次连接时，先由服务端发送一个handshake报文给客户端。
handshake报文包含：
协议版本、服务端版本、协议支持(兼容)内容标志位（32位)、分配连接ID；
默认使用字符集，autocommit状态；
分2段的随机字符串（前8后12），用于加密登录密码；
10个无用填充字节。
客户端接收handshake后，对兼容标志进行判断（取与），带上连接信息回复，
如用户名、密码、字集、连接到哪个DB等。
服务端回复OK后，握手完成。
其它通信过程，即查询过程，只由客户端发起–除非svr直接close TCP。
客户端使用一个字节(0~255)，定义命令集，至今只定义20多条命令。
服务端返回报文包只有6大格式，（或称4种:ok,err,eof因除此之外可称data)；服务端根据客户端命令返回。
6大报文中包含几种结构：result-set、resutl-set-header、row-data、field、EOF、statement.
注意事项
报文格式在4.0以前与4.1后有变动，所以兼容标志位有标识是否使用4.1以上。
由于4.0及以下版本太旧，无必要支持与解释。
所谓的兼容标志，其实是双方支持使用方式与格式的通信，诸如双方是否支持ssl，compress等。
包序号：每次应答开次的第一个包序号必需是从零开始，当大于255后，回滚为0.
对于大于16M字节传输的包，需要对方再次去读取，即分包。
*/

package mysql

import (
	"bufio"
	"fmt"
	"io"
	"net"
)

const (
	defaultReaderSize = 8 * 1024
)

type PacketIO struct {
	rb *bufio.Reader
	wb io.Writer

	Sequence uint8
}

func NewPacketIO(conn net.Conn) *PacketIO {
	p := new(PacketIO)

	p.rb = bufio.NewReaderSize(conn, defaultReaderSize)
	p.wb = conn

	p.Sequence = 0

	return p
}

func (p *PacketIO) ReadPacket() ([]byte, error) {
	header := []byte{0, 0, 0, 0}

	if _, err := io.ReadFull(p.rb, header); err != nil {
		return nil, ErrBadConn
	}

	length := int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16)
	if length < 1 {
		return nil, fmt.Errorf("invalid payload length %d", length)
	}

	sequence := uint8(header[3])

	if sequence != p.Sequence {
		return nil, fmt.Errorf("invalid sequence %d != %d", sequence, p.Sequence)
	}

	p.Sequence++

	data := make([]byte, length)
	if _, err := io.ReadFull(p.rb, data); err != nil {
		return nil, ErrBadConn
	} else {
		if length < MaxPayloadLen {
			return data, nil
		}

		var buf []byte
		buf, err = p.ReadPacket()
		if err != nil {
			return nil, ErrBadConn
		} else {
			return append(data, buf...), nil
		}
	}
}

//data already have header
func (p *PacketIO) WritePacket(data []byte) error {
	length := len(data) - 4

	for length >= MaxPayloadLen {

		data[0] = 0xff
		data[1] = 0xff
		data[2] = 0xff

		data[3] = p.Sequence

		if n, err := p.wb.Write(data[:4+MaxPayloadLen]); err != nil {
			return ErrBadConn
		} else if n != (4 + MaxPayloadLen) {
			return ErrBadConn
		} else {
			p.Sequence++
			length -= MaxPayloadLen
			data = data[MaxPayloadLen:]
		}
	}

	data[0] = byte(length)
	data[1] = byte(length >> 8)
	data[2] = byte(length >> 16)
	data[3] = p.Sequence

	if n, err := p.wb.Write(data); err != nil {
		return ErrBadConn
	} else if n != len(data) {
		return ErrBadConn
	} else {
		p.Sequence++
		return nil
	}
}

func (p *PacketIO) WritePacketBatch(total, data []byte, direct bool) ([]byte, error) {
	if data == nil {
		//only flush the buffer
		if direct == true {
			n, err := p.wb.Write(total)
			if err != nil {
				return nil, ErrBadConn
			}
			if n != len(total) {
				return nil, ErrBadConn
			}
		}
		return total, nil
	}

	length := len(data) - 4
	for length >= MaxPayloadLen {

		data[0] = 0xff
		data[1] = 0xff
		data[2] = 0xff

		data[3] = p.Sequence
		total = append(total, data[:4+MaxPayloadLen]...)

		p.Sequence++
		length -= MaxPayloadLen
		data = data[MaxPayloadLen:]
	}

	data[0] = byte(length)
	data[1] = byte(length >> 8)
	data[2] = byte(length >> 16)
	data[3] = p.Sequence

	total = append(total, data...)
	p.Sequence++

	if direct {
		if n, err := p.wb.Write(total); err != nil {
			return nil, ErrBadConn
		} else if n != len(total) {
			return nil, ErrBadConn
		}
	}
	return total, nil
}
